/*********************************************************************
*
* Software License Agreement (BSD License)
*
*  Copyright (c) 2010, Heriot-Watt University, UK.
*  All rights reserved.
*
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions
*  are met:
*
*   * Redistributions of source code must retain the above copyright
*     notice, this list of conditions and the following disclaimer.
*   * Redistributions in binary form must reproduce the above
*     copyright notice, this list of conditions and the following
*     disclaimer in the documentation and/or other materials provided
*     with the distribution.
*   * Neither the name of the Heriot-Watt University nor the names of
*     its contributors may be used to endorse or promote products
*     derived from this software without specific prior written
*     permission.
*
*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
*  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
*  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
*  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
*  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
*  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
*  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
*  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
*  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
*  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
*  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
*  POSSIBILITY OF SUCH DAMAGE.
*
*  Author: Joel Cartwright
*
*********************************************************************/

#include <iostream>

#include <matchmaker/advanced_client.h>

#include <boost/foreach.hpp>

namespace matchmaker
{

// ------------------------------------------------------------
// Public methods
// ------------------------------------------------------------

AdvancedClient::AdvancedClient()
{

}


AdvancedClient::~AdvancedClient()
{
  shutdown();
}


void AdvancedClient::shutdown()
{
  if (!active_) return;

  // AdvancedClient::shutdown() will be called twice on destruction,
  // Once from here, and again from the superclass destructor.
  // The second call will do nothing, though.
  Client::shutdown();
}


ServiceQuery AdvancedClient::queryService(const std::string & serviceName)
{
  if (!active_) {
    std::cerr << "Matchmaker AdvancedClient was incorrectly initialised." << std::endl;
    std::cerr << "Check errors output during construction." << std::endl;
    return ServiceQuery();
  }

  boost::lock_guard<boost::mutex> lock(classMutex_);

  ServiceQuery query(serviceName);

  // Do polled query the first time.
  ros::Time resultsTime = ros::Time::now();
  if (!retrieveServicesByName(serviceName, &query.impl_->results_)) {
    ROS_WARN_STREAM("queryService: Failed to perform initial query for '" << serviceName << "'; check matchmaker");
    return ServiceQuery();
  }

  query.setResultsChangeTime(resultsTime);

  // Now add the monitor to the map, for future automatic query updates.
  serviceQueryMap_[serviceName] = query;

  ROS_DEBUG_STREAM("Created service query for '" << serviceName << "'");

  scheduleSendClientUpdateMsg();

  return query;
}


ServiceQuery AdvancedClient::queryService(const std::string & serviceName,
    boost::function<void (const ServiceQuery& query)> callback,
    float forcedCallbackInterval)
{
  if (!active_) {
    std::cerr << "Matchmaker AdvancedClient was incorrectly initialised." << std::endl;
    std::cerr << "Check errors output during construction." << std::endl;
    return ServiceQuery();
  }

  // Need to instantiate monitor and status objects here, because
  // we want to use a scoped lock below.
  ServiceQuery query;

  // Block for scoped lock on big class mutex.
  {
    boost::lock_guard<boost::mutex> lock(classMutex_);

    query = ServiceQuery(serviceName, callback);

    // Do polled query the first time.
    ros::Time resultsTime = ros::Time::now();
    if (!retrieveServicesByName(serviceName, &query.impl_->results_)) {
      ROS_WARN_STREAM("queryService: Failed to perform initial query for '" << serviceName << "'; check matchmaker");
      return ServiceQuery();
    }

    query.setResultsChangeTime(resultsTime);

    // Add the monitor to the map.
    serviceQueryMap_[serviceName] = query;

    if (forcedCallbackInterval > 0)
    {
      ros::Timer timer = nh_->createTimer(ros::Duration(forcedCallbackInterval),
          boost::bind(&ServiceQuery::performCallback, query));

      query.storeTimer(timer);
      ROS_DEBUG_STREAM("Created service query for '" << serviceName << "' with callback and interval " << forcedCallbackInterval);
    }
    else
    {
      ROS_DEBUG_STREAM("Created service query for '" << serviceName << "' with callback.");
    }
  }

  // With the mutex unlocked, now safely perform the callback.
  query.performCallback();

  scheduleSendClientUpdateMsg();

  return query;
}


ServiceQuery AdvancedClient::queryAllServices(boost::function<void (const ServiceQuery& query)> callback,
    float forcedCallbackInterval)
{
  return queryService(std::string(), callback, forcedCallbackInterval);
}


ServiceQuery AdvancedClient::queryAllServices()
{
  return queryService("");
}


// ------------------------------------------------------------
// Protected internal methods
// ------------------------------------------------------------


bool AdvancedClient::retrieveAllServices(L_ServiceDetails *services)
{
  assert(services != NULL);
  services->clear();

  matchmaker_msgs::FindServices srv;
  srv.request.query_type = matchmaker_msgs::FindServices::Request::QUERY_TYPE_ALL;
  std::string errorString;
  if (!doCheckedQueryForServices(srv, &errorString)) {
    ROS_ERROR_STREAM("retrieveServices: Failed: " << errorString);
    return false;
  }

  ROS_DEBUG_STREAM("retrieveServices: Query successful");

  convertServicesFromQuery(srv, services);

  return true;
}


bool AdvancedClient::retrieveServicesByName(const std::string & serviceName, L_ServiceDetails *services)
{
  assert(services != NULL);
  services->clear();

  matchmaker_msgs::FindServices srv;
  srv.request.query_type = matchmaker_msgs::FindServices::Request::QUERY_TYPE_PROVIDING;
  srv.request.services.push_back(serviceName);
  std::string errorString;
  if (!doCheckedQueryForServices(srv, &errorString)) {
    ROS_ERROR_STREAM("retrieveAllProviding: Failed: " << errorString);
    return false;
  }

  ROS_DEBUG_STREAM("retrieveAllProviding('" << serviceName << "'): Query successful");

  convertServicesFromQuery(srv, services);

  return true;
}


} // namespace
